package br.ufrgs.meetapp.models;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;

import android.location.Location;
import android.net.ConnectivityManager;
import br.ufrgs.meetapp.enums.MessageCode;
import br.ufrgs.meetapp.enums.PositionEventCode;


/***
 * Model for the position message. Extends the MFTMessage class. Provides the possibility of serialization according to the MFT protocol 
 * @author Benites
 *
 */
public class PositionMessage extends MFTMessage {

	private final int MESSAGE_BODY_SIZE_BYTES = 64;

	private final int POSITION_EVENT_CODE_INDEX = 4;
	private final int POSITION_DATE_TIME_INDEX = 5;
	private final int POSITION_MCC_INDEX = 13;
	private final int POSITION_MNC_INDEX = 15;
	private final int POSITION_LAC_INDEX = 17;
	private final int POSITION_CID_INDEX = 21;
	
	// TODO: Verify where to get this information and if it will be used or not
	//private final int POSITION_SIGNAL_INDEX = 25;
	private final int POSITION_IOS_INDEX = 26;
	private final int POSITION_BATTERY_LEVEL_INDEX = 27;
	private final int POSITION_BATTERY_VOLTAGE_INDEX = 28;
	private final int POSITION_GPS_FIX_TIME_INDEX = 30;
	private final int POSITION_LATITUDE_INDEX = 38;
	private final int POSITION_LONGITUDE_INDEX = 46;
	private final int POSITION_RADIUS_INDEX = 54;
	private final int POSITION_ALTITUDE_INDEX = 56;
	private final int POSITION_SPEED_INDEX = 60;
	private final int POSITION_DIRECTION_INDEX = 62;
	private final int POSITION_SATELLITES_COUNT_INDEX = 64;
	
	// TODO: Verify if this truly won't be needed
	//private final int POSITION_PDOP_INDEX = 65;
	//private final int POSITION_HDOP_INDEX = 66;
	
	private final int POSITION_PRECISION_QUALITY_MODE_INDEX = 67;

	private PositionEventCode eventCode;
	private long currentTimeMillis;
	private String mcc;
	private String mnc;
	private int lac;
	private int cellId;
	private int batteryLevel;
	private int batteryVoltage;
	private int batteryStatus;
	private boolean isRoaming;
	private int networkType;
	private Location location;
	private int satellitesCount;
	
	// TODO: verify if the type of this attributes is really int and how to get
	// them using Android API features
	private int precisionQualityMode;
	private byte signal;

	public PositionMessage(){
		
	}
	
	public PositionMessage(PositionEventCode _eventCode, long _currentTimeMillis, String _mcc, String _mnc, int _lac, int _cellId, 
			int _batteryLevel, int _batteryVoltage, int _batteryStatus, boolean _isRoaming, int _networkType, Location _location, 
			int _satellitesCount, int _precisionQualityMode, byte _signal) {
		
		eventCode = _eventCode;
		currentTimeMillis = _currentTimeMillis;
		mcc = _mcc;
		mnc = _mnc;
		lac = _lac;
		cellId = _cellId;
		batteryLevel = _batteryLevel;
		batteryVoltage = _batteryVoltage;
		batteryStatus = _batteryStatus;
		isRoaming = _isRoaming;
		networkType = _networkType;
		location = _location;
		satellitesCount = _satellitesCount;
		precisionQualityMode = _precisionQualityMode;
		signal = _signal;
	}
	
	/***
	 * Returns the serialized position message according to the MFT position protocol
	 * @return Byte array to be send to MFT listener
	 */
	@Override
	public byte[] getSerializedMessage() {

		createByteMessage(MESSAGE_BODY_SIZE_BYTES, MessageCode.POSITION.getByteCode());

		if (eventCode != null){
			writeByteAtMessageIndex(eventCode.getByteValue(),
					POSITION_EVENT_CODE_INDEX);
		}
		
		// write position date and time in seconds
		writeLongAtMessageIndex(currentTimeMillis*10000+621355968000000000L,
				POSITION_DATE_TIME_INDEX); //+621355968000000000L*10000 because we have to send date to C# in ticks (100-nanoseconds)

		writeShortAtMessageIndex(mcc, POSITION_MCC_INDEX);

		writeShortAtMessageIndex(mnc, POSITION_MNC_INDEX);

		writeIntAtMessageIndex(lac, POSITION_LAC_INDEX);

		writeIntAtMessageIndex(cellId, POSITION_CID_INDEX);

		// writeByteAtMessageIndex(signal, POSITION_SIGNAL_INDEX);

		writeIOS();

		writeBatteryLevel();

		writeBatteryVoltage();

		// writes the GPS fix date and time in seconds
		writeLongAtMessageIndex(location.getTime()*10000+621355968000000000L,
				POSITION_GPS_FIX_TIME_INDEX);//+621355968000000000L*10000 because we have to send date to C# in ticks (100-nanoseconds)

		writeDoubleAtMessageIndex(location.getLatitude(),
				POSITION_LATITUDE_INDEX);

		writeDoubleAtMessageIndex(location.getLongitude(),
				POSITION_LONGITUDE_INDEX);

		writeShortAtMessageIndex(location.getAccuracy(), POSITION_RADIUS_INDEX);

		writeIntAtMessageIndex(location.getAltitude(),
				POSITION_ALTITUDE_INDEX);
		//convert speed from m/s in Location to km/h
		writeShortAtMessageIndex((float)(location.getSpeed()*3.6), POSITION_SPEED_INDEX);

		writeShortAtMessageIndex(location.getBearing(),
				POSITION_DIRECTION_INDEX);

		writeShortAtMessageIndex(satellitesCount,
				POSITION_SATELLITES_COUNT_INDEX);

		writePrecisionQualityMode();

		finishByteMessage();

		return byteMessage;

	}
	
	
	/***
	 * Mount the IOS byte accordingly to battery status, roaming set or not set and network type 
	 */
	private void writeIOS() {
		/*
		 * 0x01. Roaming 0x02. ACLine SIGNAL 1 0x04. G-Sensor SIGNAL 3 0x08.
		 * Call Talking 0x10. Data Connection 0x20. Wifi Connected 0x40.
		 * Backlight on 0x80. GPS on // GPSDISC SIGNAL 2 - Panic SIGNAL 4 - Low
		 * Bat
		 */

		byte IOs = 0;

		// Get Battery Status
		if (batteryStatus == 1) {
			IOs += 0x02;
		}

		if (isRoaming) {
			IOs += 0x01; // Roaming
		}

		if (networkType == ConnectivityManager.TYPE_WIFI) {
			IOs += 0x20; // Wifi Connected
		}

		writeByteAtMessageIndex(IOs, POSITION_IOS_INDEX);
	}
	
	
	/***
	 * Cast the battery level to short and write just the second byte of it
	 */
	private void writeBatteryLevel() {
		if (batteryLevel > 0) {
			ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
			DataOutputStream dataOutputStream = new DataOutputStream(
					byteOutputStream);
			byte[] byteMessageTemp;

			try {
				dataOutputStream.writeShort((short) batteryLevel);
				dataOutputStream.close();
			} catch (Exception e) {
				System.out
						.println("Error in PositionMessage.writeBatteryLevel - trying to cast int to short and write in dataOutputStream");
				if (e.getMessage() != null) {
					System.out.println(e.getMessage());
				}
				e.printStackTrace();
			}

			byteMessageTemp = byteOutputStream.toByteArray();

			// write the second byte of the short to add on the battery level
			writeByteAtMessageIndex(byteMessageTemp[1], POSITION_BATTERY_LEVEL_INDEX);
		}
	}
	
	/***
	 * Verify if the battery voltage is greater than zero and write it in the corresponding index
	 */
	private void writeBatteryVoltage() {
		if (batteryVoltage > 0) {
			writeFloatAtMessageIndex(batteryVoltage,
					POSITION_BATTERY_VOLTAGE_INDEX);
		}
	}
	
	/***
	 * Mount the byte corresponding to Precision/Quality and Mode and write it in the corresponding index
	 */
	private void writePrecisionQualityMode() {

		// TODO: verify the algorithm to create this byte, for now it will be
		// fixed in 1 1 0 0 1 0 1 1

		// Initialize the byte with 0 0 0 0 0 0 0 0
		byte presionQualityMode = 0;

		// Set the 1th and the 2th less significant bits to 1 and let the 3th
		// less significant bit in 0 - Fix precision (011) - 0 0 0 0 0 0 1 1
		presionQualityMode = (byte) (presionQualityMode | (1 << 0));
		presionQualityMode = (byte) (presionQualityMode | (1 << 1));

		// Set 4th less significant bit to 1 and let 5th bit in 0 - Quality (01)
		// - 0 0 0 0 1 0 1 1
		presionQualityMode = (byte) (presionQualityMode | (1 << 3));

		// Set 7th less significant bit to 1 and let 6th bit in 0 - Mode (10) -
		// 0 1 0 0 1 0 1 1
		presionQualityMode = (byte) (presionQualityMode | (1 << 6));

		// Set 8th less significant bit to 1 - is Valid? (1) - 1 1 0 0 1 0 1 1
		presionQualityMode = (byte) (presionQualityMode | (1 << 7));
		
		writeByteAtMessageIndex(presionQualityMode, POSITION_PRECISION_QUALITY_MODE_INDEX);

	}

	public Location getLocation() {
		return location;
	}

	public void setLocation(Location location) {
		this.location = location;
	}

	public int getSatellitesCount() {
		return satellitesCount;
	}

	public void setSatellitesCount(int satellitesCount) {
		this.satellitesCount = satellitesCount;
	}

	public PositionEventCode getEventCode() {
		return eventCode;
	}

	public void setEventCode(PositionEventCode eventCode) {
		this.eventCode = eventCode;
	}

	public String getMcc() {
		return mcc;
	}

	public void setMcc(String mcc) {
		this.mcc = mcc;
	}

	public String getMnc() {
		return mnc;
	}

	public void setMnc(String mnc) {
		this.mnc = mnc;
	}

	public int getLac() {
		return lac;
	}

	public void setLac(int lac) {
		this.lac = lac;
	}

	public int getCellId() {
		return cellId;
	}

	public void setCellId(int cellId) {
		this.cellId = cellId;
	}

	public int getBatteryLevel() {
		return batteryLevel;
	}

	public void setBatteryLevel(int batteryLevel) {
		this.batteryLevel = batteryLevel;
	}

	public int getBatteryVoltage() {
		return batteryVoltage;
	}

	public void setBatteryVoltage(int batteryVoltage) {
		this.batteryVoltage = batteryVoltage;
	}

	public long getCurrentTimeMillis() {
		return currentTimeMillis;
	}

	public void setCurrentTimeMillis(long currentTimeMillis) {
		this.currentTimeMillis = currentTimeMillis;
	}

	public int getPrecisionQualityMode() {
		return precisionQualityMode;
	}

	public void setPrecisionQualityMode(int precisionQualityMode) {
		this.precisionQualityMode = precisionQualityMode;
	}

	public byte getSignal() {
		return signal;
	}

	public void setSignal(byte signal) {
		this.signal = signal;
	}

	public int getBatteryStatus() {
		return batteryStatus;
	}

	public void setBatteryStatus(int batteryStatus) {
		this.batteryStatus = batteryStatus;
	}

	public boolean isRoaming() {
		return isRoaming;
	}

	public void setRoaming(boolean isRoaming) {
		this.isRoaming = isRoaming;
	}

	public int getNetworkType() {
		return networkType;
	}

	public void setNetworkType(int networkType) {
		this.networkType = networkType;
	}

}
